Skip to content

feat: Add Qwen OAuth authentication with QR code login support#822

Open
kyeo-hub wants to merge 9 commits intosipeed:mainfrom
kyeo-hub:feat/qwen-oauth-with-uuid
Open

feat: Add Qwen OAuth authentication with QR code login support#822
kyeo-hub wants to merge 9 commits intosipeed:mainfrom
kyeo-hub:feat/qwen-oauth-with-uuid

Conversation

@kyeo-hub
Copy link

📝 Description

What this PR does

Implements Qwen OAuth authentication using the device code flow (QR code scan), allowing users to authenticate with their Qwen/Alibaba Cloud account without manually managing API keys.

Key features

  1. QR Code Login: Users authenticate by scanning QR code with their Qwen account
  2. Token Management: Automatic token refresh before expiration (5-minute buffer)
  3. OpenAI-compatible API: Uses https://portal.qwen.ai/v1/chat/completions endpoint
  4. Multiple Protocol Aliases: Supports qwen-oauth, qwenoauth, qwen-portal
  5. Default Model: coder-model (Qwen Coder for code generation and understanding)

Configuration example

{
  "model_name": "qwen-coder",
  "model": "qwen-oauth/coder-model",
  "auth_method": "oauth"
}

Usage

Login (scan QR code)
picoclaw auth login --provider qwen

Chat
picoclaw agent -m qwen-oauth "Hello!"

Documentation

  • ✅ Updated README.md and README.zh.md with Qwen OAuth configuration guide
  • ✅ Added provider comparison table entries
  • ✅ Included usage examples

Commits

  • d1f105a feat: add Qwen OAuth authentication with UUID library
  • 53306c5 refactor: revert unnecessary uuid change in feishu_64.go
  • dca090a feat: add qwen-portal alias for Qwen OAuth provider
  • ef9a14a docs: add Qwen OAuth configuration guide

kyeo-hub and others added 4 commits February 26, 2026 21:40
- Add qwen_oauth.go for Qwen Portal OAuth device code flow
- Use github.com/google/uuid instead of custom UUID generation
- Add qwen_provider.go for Qwen OAuth LLM provider
- Update auth helpers to support qwen provider login/logout
- Update factory_provider.go to support qwen-oauth protocol
- Update feishu_64.go to use Google uuid library

Fixes reviewer comments about using Google uuid library instead of
custom implementation.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

Add qwen-portal as an alternative protocol prefix for Qwen OAuth authentication
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

Add documentation for Qwen OAuth (QR code login) in both README.md and README.zh.md
@yinwm
Copy link
Collaborator

yinwm commented Feb 26, 2026

Code Review: Qwen OAuth Authentication

Thanks for this contribution! The overall implementation looks solid. Here are my findings:


✅ What's Done Well

  1. PKCE Implementation - Uses S256 challenge method, compliant with RFC 7636
  2. Auto Token Refresh - 5-minute buffer design is reasonable
  3. Error Handling - Comprehensive OAuth error state coverage (authorization_pending, slow_down, expired_token, access_denied)
  4. Documentation - Both README.md and README.zh.md are updated

🔴 Critical Issues

1. helpers.go:204-220 - Forcefully Overwrites User Configuration

```go
if isQwenModel(appCfg.ModelList[i].Model) {
appCfg.ModelList[i].Model = "qwen-portal/coder-model" // Forced overwrite!
appCfg.ModelList[i].APIBase = "https://portal.qwen.ai/v1"
appCfg.ModelList[i].APIKey = "qwen-oauth"
appCfg.ModelList[i].AuthMethod = "oauth"
found = true
break
}
```

Problem: If a user has already configured a custom qwen model (e.g., `qwen/qwen-max`), it will be forcibly changed to `coder-model` after login.

Suggestion: Only update `AuthMethod` and credential-related fields, preserve the original model configuration.

2. helpers.go:224 - Silently Modifies Default Model

```go
appCfg.Agents.Defaults.ModelName = "qwen-coder"
```

Problem: After login, the user's default model is automatically changed to qwen-coder without consent. This is too aggressive.


🟡 Medium Issues

3. qwen_oauth.go:354, 384, 528 - HTTP Client Created Repeatedly

```go
client := &http.Client{Timeout: 15 * time.Second} // New client every request
```

Suggestion: Reuse HTTP client, or use `sync.Once` for lazy initialization.

4. qwen_oauth.go:577-580 - Refresh Failure Silently Ignored

```go
newCred, refreshErr := RefreshQwenCredentials(cred)
if refreshErr == nil {
_ = SetCredential("qwen", newCred)
return newCred.AccessToken, nil
}
// Refresh failed but token may still be valid; fall through
```

Problem: No logging when refresh fails, users won't know their token is about to expire.

5. qwen_provider.go:716-717 - Repeated TrimPrefix Calls

```go
model = strings.TrimPrefix(model, "qwen-oauth/")
model = strings.TrimPrefix(model, "qwen/")
```

Could be simplified or extracted to a utility function.

6. Code Duplication - `convertMessagesForQwen` and `convertToolsForQwen`

These functions are nearly identical to implementations in other providers (OpenAI, DeepSeek). Consider extracting to a common module.


🟢 Minor Issues

7. qwen_oauth.go:589-595 - Unused Parameter

```go
func printSimpleQRHint(verifyURL string) {
// ...
_ = verifyURL // Parameter is completely unused
}
```

If `verifyURL` isn't used, don't pass it as a parameter.

8. helpers.go:181-186 - Incomplete `isQwenModel` Check

```go
func isQwenModel(model string) bool {
return model == "qwen" ||
model == "qwen-oauth" ||
strings.HasPrefix(model, "qwen/") ||
strings.HasPrefix(model, "qwen-oauth/")
}
```

Missing the `qwen-portal` alias which is supported in `factory_provider.go`.


🔧 Suggested Fixes

```go
// helpers.go - Don't overwrite user configuration
if isQwenModel(appCfg.ModelList[i].Model) {
// Only update auth-related fields, preserve original model name
appCfg.ModelList[i].AuthMethod = "oauth"
if appCfg.ModelList[i].APIBase == "" {
appCfg.ModelList[i].APIBase = "https://portal.qwen.ai/v1"
}
found = true
break
}

// Remove this line - don't change default model without consent
// appCfg.Agents.Defaults.ModelName = "qwen-coder"
```


📊 Summary

Category Count
🔴 Critical 2
🟡 Medium 4
🟢 Minor 2

Recommendation: Fix the two critical issues (forced configuration overwrite) before merging. Other issues can be addressed in follow-up PRs.

- Fix gci formatting: remove trailing whitespace on line 230
- Add nolint:gosmopolitan for intentional Chinese UI strings
- Remove unused qwenPortalBaseURL constant
- Fix spelling: serialises → serializes

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
@kyeo-hub kyeo-hub force-pushed the feat/qwen-oauth-with-uuid branch from 95c9674 to 4c96503 Compare February 27, 2026 01:15
- Add qwen_oauth_test.go with 17 test cases for OAuth authentication
- Add qwen_provider_test.go with 15 test cases for Qwen provider
- Fix factory_provider.go to separate qwen and qwen-oauth protocols
- Add qwen API key and OAuth examples to config.example.json
- Export SetQwenTestEndpoints for testability

This addresses PR review feedback:
1. ✅ Add test files for CI automation
2. ✅ Fix configuration and documentation consistency

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
@yinwm
Copy link
Collaborator

yinwm commented Feb 28, 2026

Review: Qwen OAuth Authentication Implementation

Thanks for this comprehensive feature addition! The implementation is well-structured with good test coverage (32 test cases). Here are my observations:

✅ Strengths

  1. Security: PKCE (RFC 7636) implementation is correct with 32-byte random verifier and S256 challenge
  2. Error Handling: Proper handling of OAuth states (authorization_pending, slow_down, expired_token, access_denied)
  3. Token Management: Auto-refresh with 5-minute buffer is a sensible design choice
  4. Test Coverage: Comprehensive unit tests for both OAuth flow and provider logic
  5. Documentation: Complete bilingual documentation (EN/CN)

⚠️ Suggestions for Improvement

1. pollQwenToken is not cancellable (Medium Priority)

Location: pkg/auth/qwen_oauth.go:413-476

for time.Now().Before(deadline) {
    time.Sleep(pollInterval)  // Cannot be cancelled by user
    // ...
}

If a user wants to cancel the login (Ctrl+C), the polling continues until timeout.

Suggested fix:

func pollQwenToken(ctx context.Context, deviceCode, verifier string, interval, expiresIn int) (*qwenTokenResponse, error) {
    // ...
    for time.Now().Before(deadline) {
        select {
        case <-time.After(pollInterval):
        case <-ctx.Done():
            return nil, ctx.Err()
        }
        // ...
    }
}

2. printSimpleQRHint ignores its parameter (Low Priority)

Location: pkg/auth/qwen_oauth.go:625-632

func printSimpleQRHint(verifyURL string) {
    // ...
    _ = verifyURL  // Parameter unused
}

Consider adding a TODO comment if this is a placeholder for actual QR code rendering, or remove the parameter if intentionally unused.

3. Protocol prefix inconsistency (Low Priority)

Location: cmd/picoclaw/internal/auth/helpers.go:455-460

appCfg.ModelList[i].Model = "qwen-portal/coder-model"

The documentation primarily describes qwen-oauth. Consider using the canonical name consistently:

appCfg.ModelList[i].Model = "qwen-oauth/coder-model"

📝 Summary

Category Assessment
Feature Completeness ✅ Complete
Security ✅ PKCE correctly implemented
Code Quality ✅ Good
Test Coverage ✅ Comprehensive
Documentation ✅ Complete

Recommendation: Address the cancellable polling issue, then this PR is ready to merge. Great work!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants